package com.ibm.cps.processors.vector;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.crypto.Data;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ibm.cps.message.MessageFields;
import com.ibm.cps.message.ParsedDataMessage;
import com.ibm.cps.sql.expression.ExpressionNode;
import com.ibm.cps.sql.expression.FieldEvaluator;
import com.ibm.cps.sql.expression.IdentifierNode;
import com.ibm.cps.sql.expression.MapBasedFieldEvaluator;
import com.ibm.cps.sql.expression.NumberNode;
import com.ibm.datamodel.timeseries.TimeSeriesSchema;
import com.ibm.interfaces.ITimeSeriesSchema;
import com.ibm.util.DataTypes;
import com.ibm.util.ErrorCode;
import com.ibm.util.JSONUtility;
import com.ibm.util.exception.CPSException;

public class SchemaUtility {
	private SchemaUtility(){}
	
	public static FieldEvaluator createEvaluatorForAllFields(ITimeSeriesSchema schema, ParsedDataMessage message){
		List<String> fieldsRequired = new ArrayList<>();
		for (String tag : schema.getTagNames()) {
			fieldsRequired.add(tag);
		}
		return createEvaluator(schema, message, fieldsRequired);
	}
	
	public static FieldEvaluator createEvaluator(ITimeSeriesSchema schema, ParsedDataMessage message, Collection<String> fieldsRequired){
		Map<String, Object> values = new HashMap<>();
		
		for (String field : fieldsRequired) {
			int index = schema.getTagId(field);
			byte datatype = schema.getDataType(index);
			if(!DataTypes.isArray(datatype)){
				Comparable value = message.getValue(index);
				values.put(field, value);
			}else{
				// tagid is a array contain index
				Object[] value = message.getArrayValue(index);				
				values.put(field, value);
			}

		}
		FieldEvaluator evaluator = new MapBasedFieldEvaluator(values);
		return evaluator;
	}
	
	// copy parent schema
	// can just deepcopy
	// 		ArrayNode tags = parentSchema.getTags().deepCopy();
	public static ArrayNode copySchemaFromParent(ITimeSeriesSchema parentSchema){
		ArrayNode result = JSONUtility.newArrayNode();
		String[] fieldNames = parentSchema.getTagNames();
		byte[] dataTypes = parentSchema.getDataTypes();
		for(int i = 2; i < fieldNames.length; i++){
			ObjectNode field = JSONUtility.newObjectNode();
			String name = fieldNames[i];
			field.put(MessageFields.NAME, name);
			field.put(MessageFields.DATATYPE, DataTypes.getDataType(dataTypes[i]));
			result.add(field);
		}
		return result;
	}
	
	public static ArrayNode createVectorSchema(String fieldName,boolean isArray){
		ArrayNode result = JSONUtility.newArrayNode();
		
		
		if(isArray){
			ObjectNode field = JSONUtility.newObjectNode();
			field.put(MessageFields.NAME,fieldName);
			field.put(MessageFields.DATATYPE,"double[]");
			result.add(field);

		}else{
// don't need now
//			for(int i = 0; i < dimension; i++){
//				String name = fieldName + "(" + i + ")";
//				ObjectNode field = JSONUtility.newObjectNode();
//				field.put(MessageFields.NAME, name);
//				field.put(MessageFields.DATATYPE, "double");
//				result.add(field);
//			}
			ObjectNode field = JSONUtility.newObjectNode();
			field.put(MessageFields.NAME, fieldName);
			field.put(MessageFields.DATATYPE, "double");
			result.add(field);
		}
		return result;
	}
	
	// you can use parsedatamessagebuilder.copyvalue instead.
	@Deprecated
	public static void copyValues(Object[] values, ITimeSeriesSchema parentSchema, ITimeSeriesSchema outputSchema, Object[] inputValues){
		String[] parentFieldNames = parentSchema.getTagNames();
        //Copy non-temporary values from input stream
        for(int i = 0; i < parentFieldNames.length; i++){
        	String fieldName = parentFieldNames[i];
    		int fieldIndex = parentSchema.getTagId(fieldName);
    		Object value = inputValues[fieldIndex];
    		int outputIndex = outputSchema.getTagId(fieldName);
    		values[outputIndex] = value;
        }
	}
	
	public static void writeOutputVectorValues(Object[] outputDataArray, ITimeSeriesSchema outputSchema, String outputFieldName, Comparable[] outputVector){
		if(outputVector.length == 1){
        	int outputIndex = outputSchema.getTagId(outputFieldName);
        	outputDataArray[outputIndex] = outputVector[0];
        }else{
        	int outputIndex = outputSchema.getTagId(outputFieldName);
        	outputDataArray[outputIndex] = outputVector;
//        	for(int i = 0; i < outputVector.length; i++){
//        		String fieldName = outputFieldName + "(" + i + ")";
//        		int outputIndex = outputSchema.getTagId(fieldName);
//        		outputDataArray[outputIndex] = outputVector[i];
//        	}
        }
	}
	
	public static Double[] extractValues(ITimeSeriesSchema parentSchema, ParsedDataMessage inputDataMessage, ExpressionNode argument) throws CPSException{
		if(argument instanceof NumberNode){
			double value = ((NumberNode)argument).getValue();
			return new Double[]{value};
		}else if(argument instanceof IdentifierNode){
			String fieldName = ((IdentifierNode)argument).getName();
			int index = parentSchema.getTagId(fieldName);
			byte type = parentSchema.getDataType(index);
			if(!DataTypes.isArray(type)){
// 			not a array field
				double value = (Double)inputDataMessage.getValue(parentSchema.getTagId(fieldName));
				return new Double[]{value};
			}else{
// no need to do this
//				for(int i = 0; i < result.length; i++){
//					String name = fieldName + "(" + i + ")";
//					double value = ((Number)inputDataMessage.getValue(parentSchema.getTagId(name))).doubleValue();
//					result[i] = value;
//				}
//				Object[] _tmp = inputDataMessage.getArrayValue(parentSchema.getTagId(fieldName));
				Double[] _tmp = inputDataMessage.getArray(parentSchema.getTagId(fieldName));
				Double[] result = new Double[_tmp.length];
				for (int i = 0; i < _tmp.length; i ++) {
					result[i] = (Double) _tmp[i];
				}
				return result;
			}
		}else{
			throw new CPSException(ErrorCode.INVALID_INPUT_ERROR, "Unexpected operand type " + argument.getClass() + " in VectorCalculationProcessor");
		}
	}
}
